package com.wordpress.music2dot0.server;

import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.NoSuchElementException;
import java.util.Timer;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

/**
 * General playlist that can be generated with Jamendo data
 * 
 * @author raf
 * 
 */
@XmlRootElement(name = "data")
public class Playlist {
	private final static String API_URL = "http://api.jamendo.com/get2/";
	private final static String ITEM_UNIT = "id+name+url+stream+duration"
			+ "+album_name+album_image+artist_name+tag_idstr/";
	private final static String LOAD_TYPE = "track/";
	private final static String FORMAT_NAME = "xml/";
	private final static String TARGET_NAME = "track_album+album_artist/";

	/**
	 * Creates a new playlist filled with a given number of songs of a genre
	 * 
	 * @param size
	 *            the size of the new playlist
	 * @param genre
	 *            the genre of the new playlist
	 * @return a new playlist with a number of random songs of the given genre
	 */
	public static Playlist create(int size, String genre) {
		// filling the list purely randomly is inefficient in Jamendo
		try {
			final URL xmlLocation = new URL(API_URL + ITEM_UNIT + LOAD_TYPE
					+ FORMAT_NAME + TARGET_NAME + "?n=" + size + "&tag_idstr="
					+ genre + "&order=random&streamencoding=mp31");
			return create(xmlLocation);
		} catch (final MalformedURLException e) {
			LoginServer.log.throwing("Playlist", "create", e);
		}
		return null;
	}

	private static Playlist create(URL xmlLocation) {
		Playlist playlist = null;
		try {
			final JAXBContext jaxbContext = JAXBContext
					.newInstance(Playlist.class);
			final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
			playlist = (Playlist) unmarshaller.unmarshal(xmlLocation);
			LoginServer.log.fine("New playlist: " + playlist.toString());
		} catch (final JAXBException e) {
			LoginServer.log.throwing("Playlist", "create", e);
		}
		return playlist;
	}

	@XmlElement
	private Song current;

	@XmlElement
	Deque<Song> track = new ArrayDeque<Song>();

	Timer playTimer = new Timer();

	/**
	 * Adds a number of random songs to the playlist of a given genre
	 * 
	 * @param size
	 *            the number of songs to add
	 * @param genre
	 *            the genre of songs to add
	 */
	public void addGenre(int size, String genre) {
		merge(create(size, genre));
	}

	/**
	 * Retrieves the currently playing song
	 * 
	 * @return the song that is currently playing
	 */
	public Song getCurrent() {
		return this.current;
	}

	/**
	 * Merges a new playlist with the current one
	 * 
	 * @param p
	 *            the new playlist
	 */
	public void merge(Playlist p) {
		// null would usually happen when an API call has failed
		if (p.track != null) {
			this.track.addAll(p.track);
		}
	}

	/**
	 * Send the next song in the playlist and discard the previous one
	 * 
	 * @param group
	 *            the group for which the song should be sent
	 */
	public void playNext(GroupServer group) {
		try {
			this.current = this.track.pop();
			this.current.setGroup(group);
			this.playTimer.schedule(this.current, this.current.duration * 40);
			LoginServer.log.info("Playing \"" + this.current.name + "\" for "
					+ this.current.duration + " seconds");
		} catch (final NoSuchElementException e) {
			LoginServer.log.info("Playlist is empty, adding a random song");
			final Playlist p = Playlist.create(1, this.current.tag_idstr);
			merge(p);
			playNext(group);
		}
	}

	@Override
	public String toString() {
		final TransformerFactory tf = TransformerFactory.newInstance();
		try {
			final Transformer serializer = tf.newTransformer();
			final DOMSource source = new DOMSource(Connection.marshal(this));
			final StreamResult result = new StreamResult(new StringWriter());
			serializer.transform(source, result);
			return result.getWriter().toString();
		} catch (final TransformerConfigurationException e) {
			e.printStackTrace();
		} catch (final TransformerException e) {
			e.printStackTrace();
		}
		return null;
	}
}
